﻿using System;
using System.Collections.Generic;
using System.Windows.Threading;
using Pfz.Collections;

namespace Pfz.Extensions
{
	/// <summary>
	/// Adds the BeginInvokeOnce method to the Dispatcher class.
	/// </summary>
	public static class PfzDispatcherExtensions
	{
		private sealed class InternalAutoTrimDictionary:
			AutoTrimDictionary<KeyValuePair<Dispatcher, Action>, DispatcherOperation>
		{
			internal new object DisposeLock
			{	
				get
				{
					return base.DisposeLock;
				}
			}
		}
		private static readonly InternalAutoTrimDictionary _pendingActions = new InternalAutoTrimDictionary();

		/// <summary>
		/// Invokes the given Action only once.
		/// That is, if a call was already made for an action and not yet processed, then the new 
		/// call to BeginInvokeOnce will do nothing.
		/// </summary>
		public static DispatcherOperation BeginInvokeOnce(this Dispatcher dispatcher, Action action, DispatcherPriority priority = DispatcherPriority.Normal)
		{
			if (dispatcher == null)
				throw new ArgumentNullException("dispatcher");

			if (action == null)
				throw new ArgumentNullException("action");

			DispatcherOperation result = null;
			var pair = new KeyValuePair<Dispatcher, Action>(dispatcher, action);
			lock(_pendingActions.DisposeLock)
			{
				if (!_pendingActions.TryGetValue(pair, out result))
				{
					result = dispatcher.BeginInvoke(action, priority, null);
					_pendingActions.Add(pair, result);
					result.Completed += (sender, args) => {lock(_pendingActions){_pendingActions.Remove(pair);}};
					result.Aborted += (sender, args) => {lock(_pendingActions){_pendingActions.Remove(pair);}};

					switch(result.Status)
					{
						case DispatcherOperationStatus.Aborted:
						case DispatcherOperationStatus.Completed:
							_pendingActions.Remove(pair);
							break;
					}
				}
			}

			return result;
		}

		/// <summary>
		/// Verifies if a dispatcherOperation is a null instance, or if its status is finished.
		/// </summary>
		public static bool IsNullOrFinished(this DispatcherOperation dispatcherOperation)
		{
			if (dispatcherOperation == null)
				return true;

			switch(dispatcherOperation.Status)
			{
				case DispatcherOperationStatus.Completed:
				case DispatcherOperationStatus.Aborted:
					return true;
			}

			return false;
		}
	}
}
